#include "sqlite_database.h"

#define NOT_CONNECTED "Not connected!"

struct my_record_set {
  record_set *rs;
  unsigned int counter;

  my_record_set() : rs(0), counter(0) {
  }
};

static int callback(void *parameter, int argc, char **argv, char **column_name) {
  if (parameter == 0)
    return 0;

  my_record_set *mrs = (my_record_set*) parameter;
  ++mrs->counter;

  if (mrs->counter >= mrs->rs->get_row_count())
    mrs->rs->reserve(1 + (unsigned int) (mrs->counter * 1.5), argc);

  if (mrs->counter == 1) { // column names
    for (int j = 0; j < argc; ++j)
      mrs->rs->fill_record_set(column_name[j], strlen(column_name[j]));
  }

  for (int j = 0; j < argc; ++j) { // column data
    char *value = argv[j];
    if (value != 0)
      mrs->rs->fill_record_set(value, strlen(value));
    else
      mrs->rs->fill_record_set(0, 0);
  }

  return 0;
}

void sqlite_database::connect(const string &connection_string, const string &protocol_file) {
  if (connected == true)
    disconnect();

  protocol = protocol_file;

  const int rc = sqlite3_open(connection_string.c_str(), &db);
  if (rc != 0) {
    string tmp = sqlite3_errmsg(db);
    tmp += '\n';
    tmp += connection_string;
    throw db_exception(tmp);
  }

  connected = true;
  prot("CON", "sqlite_database", 0);
}

void sqlite_database::connect(const string &host, const string &user_id, const string &password, const string &db_name, const string &protocol_file) {
  throw not_supported_exception("Use the other overloaded connect method!");
}

void sqlite_database::disconnect() {
  if (db != 0) {
    rollback();

    sqlite3_close(db);
    db = 0;
    connected = false;
    prot("DCO", "sqlite_database", 0);
  }
}

bool sqlite_database::is_connected() {
  return (connected || db != 0);
}

void sqlite_database::execute_query(const string &s, record_set &rs, bool append_to_record_set) {
  if (append_to_record_set == false)
    rs.reconstruct(0, 0);

  if (db == 0)
    throw db_exception(NOT_CONNECTED);

  my_record_set mrs;
  mrs.rs = &rs;
  mrs.counter = rs.get_row_count();

  char *error_message = 0;
  const int rc = sqlite3_exec(db, s.c_str(), callback, &mrs, &error_message);
  if (rc != 0) {
    string tmp = error_message;
    sqlite3_free(error_message);

    rollback();
    tmp += '\n';
    tmp += s;
    throw db_exception(tmp);
  }

  if (mrs.counter > 0)
    rs.cut_rows_after(mrs.counter);

  prot("QRY", s, 0, rs.get_row_count());
}

void sqlite_database::execute_update(const string &s) {
  if (db == 0)
    throw db_exception(NOT_CONNECTED);

  char *error_message = 0;
  const int rc = sqlite3_exec(db, s.c_str(), callback, 0, &error_message);
  if (rc != 0) {
    string tmp = error_message;
    sqlite3_free(error_message);
    tmp += '\n';
    tmp += s;
    throw db_exception(tmp);
  }

  prot("UPD", s, 0);
}

void sqlite_database::start_transaction() {
  if (is_in_transaction == false) {
    execute_update("BEGIN");
    is_in_transaction = true;
    prot("TRA", "STARTED", 0);
  }
}

void sqlite_database::commit() {
  if (is_in_transaction == true) {
    execute_update("COMMIT;");
    is_in_transaction = false;
    prot("TRA", "COMMIT", 0);
  }
}

void sqlite_database::rollback() {
  if (is_in_transaction == true) {
    execute_update("ROLLBACK;");
    is_in_transaction = false;
    prot("TRA", "ROLLBACK", 0);
  }
}

void sqlite_database::lock_table(const string &table_name) {
  // nothing to do
  // sqlite needs no locking since only one process can write into the file
}

void sqlite_database::unlock_table(const string &table_name) {
  // nothing to do
  // sqlite needs no locking since only one process can write into the file
}

sqlite_database::sqlite_database() : db(0) {
}

sqlite_database::~sqlite_database() {
  disconnect();
}
